import pygame
from pygame.locals import *
import G
import math
import Util
import Timekeeper
import re
import Vector
import copy
import Configurable
import Config
import Animation

max_projectile_gap = Config.max_projectile_gap
frame_rate = Config.frame_rate

rem_extension = re.compile(r'(.*?)\.\w{3,4}$')

homedict = Config.homedict

proj_images = dict()
target_check_interval = Config.target_check_interval

def fire(base_proj, source, start_x, start_y, vel_x, vel_y, friendly,
         group=G.projectiles):
   proj = copy.deepcopy(base_proj)
   proj.source = source
   if vel_x > vel_y:
      if vel_x > -1 * vel_y:
         proj.image, proj.rect = base_proj.images[3]
      else:
         proj.image, proj.rect = base_proj.images[0]
   else:
      if vel_x > -1 * vel_y:
         proj.image, proj.rect = base_proj.images[2]
      else:
         proj.image, proj.rect = base_proj.images[1]
   proj.rect = pygame.Rect((0,0), proj.rect.size)
   proj.rect.center = [start_x, start_y]
   proj.velocity = [vel_x, vel_y]
   if proj.top_speed == 0 or proj.acceleration == 0:
      proj.ideal_velocity = proj.velocity
   elif proj.acceleration > 0:
      proj.ideal_velocity = Vector.set_length(proj.velocity, proj.top_speed)
   else:
      proj.ideal_velocity = Vector.set_length(proj.velocity, proj.top_speed)
      proj.acceleration = -1 * proj.acceleration
   if proj.home_type != 0:
      proj.target = source.target
   proj.friendly = friendly
   group.add(proj)

class Projectile(pygame.sprite.Sprite, Configurable.Configurable, Timekeeper.Timekeeper):
   runtime_vars = ('image',
                   'images',
                   'velocity',
                   'ideal_velocity',
                   'damage_real',
                   'move_xbuffer',
                   'move_ybuffer',
                   'friendly',
                   'colliding',
                   'source',
                   'target',
                   'age',
                   'target_acquire_counter')
                
   __slots__ = ('imagepath',
                'name',
                'damage',
                'type',
                'hits',
                'colorkey',
                'acceleration',
                'top_speed',
                'home_turn_speed',
                'lifetime',
                'home_type',
                'explode_damage',
                'explode_radius',
                'explode_lifetime') + runtime_vars
   
   def __init__(self, imagepath, damage, type='energy', hits=1, name=None,
                acceleration=0, top_speed=0, home_turn_speed=0, lifetime=5,
                home_type='dumb', explode_damage=0, explode_radius=0,
                colorkey=None,
                explode_lifetime=Config.explosion_lifetime, description=""):
      pygame.sprite.Sprite.__init__(self)
      Configurable.Configurable.__init__(self, locals())
      self.images = [[] for i in range(0, 4)]
      for i in range(0,len(self.images)):
         im = Util.load_image(imagepath, colorkey=colorkey, orientation=i)
         self.images[i] = im
      if name is None:
         self.name = rem_extension.search(imagepath).group(1)
      else:
         self.name = name
      self.type = type
      if acceleration == 0:
         self.damage_real = self.damage
      else:
         self.damage_real = 0
      self.move_xbuffer = 0.0
      self.move_ybuffer = 0.0
      self.colliding = None
      self.age = 0.0
      self.home_type = homedict[home_type]
      self.target_acquire_counter = 0
      if self.home_turn_speed != 0:
         self.home_turn_speed = math.pi * self.home_turn_speed / 180
   
   def acquiretarget(self):
      if self.target_acquire_counter == 0:
         if self.friendly:
            sprites = [s for s in G.enemy_ships.sprites() if not s.stealthed]
         else:
            sprites = [s for s in G.friendly_ships.sprites() if not s.stealthed]
         if len(sprites) > 0:
            target = sprites[0]
            min_angle = math.fabs(Vector.angle(self.velocity) - 
                                  Vector.angle(Vector.v_sub(target.rect.center, self.rect.center)))
            for sprite in sprites:
               angle = math.fabs(Vector.angle(self.velocity) - 
                                  Vector.angle(Vector.v_sub(sprite.rect.center, self.rect.center)))
               if angle < min_angle:
                  min_angle = angle
                  target = sprite
            self.target = target
         else:
            self.target = None
      self.target_acquire_counter = (self.target_acquire_counter + 1) % target_check_interval

   def detonate(self):
      Animation.explode(self.rect.center, self.explode_damage,
                        self.explode_radius, self.explode_lifetime,
                        self.source)
   
   def die(self):
      if self.explode_radius > 0:
         self.detonate()
      self.kill()
   
   def elongate(self, speed):
      gap = speed / frame_rate - self.images[0][1].height
      if gap >= max_projectile_gap:
         diff = int(math.ceil(gap - max_projectile_gap))
         self.images[0][1].height += diff
         self.images[2][1].height += diff
         self.images[1][1].width += diff
         self.images[3][1].width += diff
   
   def move(self, move_x, move_y):
      self.rect.move_ip(move_x, move_y)
      if not self.rect.colliderect(G.game.expanded_screen):
         self.kill()
      
      if self.friendly:
         ships = G.enemy_ships
      else:
         ships = G.friendly_ships
      collisions = pygame.sprite.spritecollide(self, ships, False)
      if len(collisions) > 0:
         def dist(spr):
            return Vector.distance(self.rect.center, spr.rect.center)
         collisions.sort(key=dist)
         cur_vel = Vector.length(self.velocity)
         if self.acceleration != 0 and self.top_speed != 0:
            self.damage_real = self.damage * cur_vel / self.top_speed
         for spr in collisions:
            if spr is not self.colliding and spr.onscreen:
               spr.hit(self)
               if self.hits > 0:
                  self.hits -= 1
               if self.hits == 0:
                  self.die()
                  break
         if self.colliding is None:
            self.colliding = collisions[-1]
      else:
         if self.colliding is not None:
            self.colliding = None
   
   def update(self):
      frame_time = self.frame()
      self.age += frame_time
      if self.age > self.lifetime:
         self.die()
         return
      if self.home_type != 0 and self.target is not None and self.target.alive():
         if self.home_type == 1:
            dest = self.target.rect.center
            angle_needed = Vector.angle_between(self.velocity, 
                     Vector.v_sub(self.target.rect.center, self.rect.center))
         elif self.home_type == 2:
            dest = Vector.lead_target(self.rect.center,
                                              Vector.length(self.velocity),
                                              self.target.rect.center,
                                              self.target.velocity)
            if dest is None:
               dest = self.target.rect.center
         angle_needed = Vector.angle_between(self.velocity, 
                  Vector.v_sub(dest, self.rect.center))
                        
         if math.fabs(angle_needed) < self.home_turn_speed * frame_time:
            self.velocity = Vector.rotate(self.velocity, -1 * angle_needed)
         else:
            if angle_needed > 0:
               self.velocity = Vector.rotate(self.velocity, self.home_turn_speed * frame_time * -1)
            else:
               self.velocity = Vector.rotate(self.velocity, self.home_turn_speed * frame_time)
               
         pos = self.rect.center
         if self.velocity[0] > self.velocity[1]:
            if self.velocity[0] > -1 * self.velocity[1]:
               self.image, self.rect = Util.load_image(self.imagepath, orientation=3)
            else:
               self.image, self.rect = Util.load_image(self.imagepath, orientation=0)
         else:
            if self.velocity[0] > -1 * self.velocity[1]:
               self.image, self.rect = Util.load_image(self.imagepath, orientation=2)
            else:
               self.image, self.rect = Util.load_image(self.imagepath, orientation=1)
         self.ideal_velocity = Vector.set_length(self.velocity, self.top_speed)
         self.rect.center = pos
      elif self.home_type != 0  and (self.target is None or not self.target.alive()):
         self.acquiretarget()
      if self.acceleration != 0:
         cur_velocity = Vector.length(self.velocity)
         if abs(cur_velocity - self.top_speed) > 1.0:
            self.velocity = Vector.v_add(self.velocity, 
                            Vector.towards(self.velocity, self.ideal_velocity, 
                                           self.acceleration * frame_time))
      self.move_xbuffer += self.velocity[0] * frame_time
      self.move_ybuffer += self.velocity[1] * frame_time
      move_x, self.move_xbuffer = divmod(self.move_xbuffer, 1.0)
      move_y, self.move_ybuffer = divmod(self.move_ybuffer, 1.0)
      self.move(int(move_x), int(move_y))
